package org.trc.traffic.analysis;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.swing.JFileChooser;

import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.shapefile.shp.ShapefileException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.CRS;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.operation.distance.DistanceOp;


public class STRegionCongestionClustering extends STMLinkCongestionClustering { 
	double timeCost = 0;
	
	protected float maxRegionDistance;

	protected int maxRegionTimeInterval;
	
	public void buildCongestionCluster(String regionID, int startHour, int endHour, Date day, float maxDistance, int maxTimeInterval) {
		if(road == null)
			this.initial();
		
		timeCost = System.currentTimeMillis();
		
		this.maxRegionDistance = maxDistance;
		
		this.maxRegionTimeInterval = maxTimeInterval;
		
	    //数据集初始化
		ArrayList <MLinkCongestionCluster> allMLinkClusterList = new ArrayList <MLinkCongestionCluster> ();
		
		Set <String> mLinkIDSet = road.multiLinkCollection.keySet(); // regionID 默认为整个路网
		
		for(String mLinkID : mLinkIDSet){
			super.buildCongestionCluster(mLinkID, startHour, endHour, day, 300, 1);
			allMLinkClusterList.addAll((Collection<? extends MLinkCongestionCluster>) super.getCongestionCluster());
			//allMLinkClusterList.addAll((Collection<? extends MLinkCongestionCluster>) super.getVIPCongestionCluster(2, 100));
		}
		
		Map<String, CongestionCluster> regionCongestionClusterSet = Collections.synchronizedMap(new HashMap<String, CongestionCluster>());
		
		//MLinkCongestionCluster分组
		
		int clusterNum = 1;
		
		MLinkCongestionCluster mLinkCC = null;
		
		for (int i = 0; i < allMLinkClusterList.size(); i++) {
			
			mLinkCC = (MLinkCongestionCluster)allMLinkClusterList.get(i);
			
			if (mLinkCC.clusterID.startsWith("Mlink")) {// 未分组  
				
				this.expandCongestionCluster(mLinkCC, allMLinkClusterList,  regionID, clusterNum + "_" + dateFormat.format(day));
				
				clusterNum++;
				
			}
			
		}
		
		//生成 MLinkCongestionCluster
		
		RegionCongestionCluster congestionCluster = null;
		
		for (MLinkCongestionCluster lCC : allMLinkClusterList){
			
			String clusterID = lCC.clusterID;
			
			if (!regionCongestionClusterSet.containsKey(clusterID)) {
				
				congestionCluster = new RegionCongestionCluster(clusterID, regionID);
				
				regionCongestionClusterSet.put(clusterID, congestionCluster);
				
			}
			congestionCluster = (RegionCongestionCluster)regionCongestionClusterSet.get(clusterID);
					
			//System.out.println("RegionClusterID: " + clusterID + " MLinkID: " + lCC.mLinkID + ", StartOffset: " + lCC.startOffset + ", EndOffset: " + lCC.endOffset);
			
			//System.out.println(this.road.getLineByLinearReference(lCC.mLinkID, lCC.startOffset, lCC.endOffset).getEnvelopeInternal());
			
			congestionCluster.add(lCC, this.road.getLineByLinearReference(lCC.mLinkID, lCC.startOffset, lCC.endOffset).getEnvelopeInternal());
		}
				
		//	
		this.congestionClusterSet = regionCongestionClusterSet;
		
		timeCost =  System.currentTimeMillis() - this.timeCost;
		
	}	

	private void expandCongestionCluster(MLinkCongestionCluster coreMLinkCluster, ArrayList<MLinkCongestionCluster> allMLinkClusterList, String regionID, String clusterNum){			
		coreMLinkCluster.clusterID = "Region_"+ regionID + "_" + clusterNum;
		
		List<MLinkCongestionCluster> resultDataSet = this.STRangeQuery(coreMLinkCluster, allMLinkClusterList);
		
		if(resultDataSet.size() == 0)
			return;
	
		for (MLinkCongestionCluster mlCC : resultDataSet) {// 标记簇ID
			if(mlCC.clusterID.startsWith("Mlink"))
				expandCongestionCluster(mlCC, allMLinkClusterList, regionID, clusterNum);
		}	
			
	}
	
	// 时空邻域查询
	private List<MLinkCongestionCluster> STRangeQuery(MLinkCongestionCluster coreMLinkCluster, ArrayList<MLinkCongestionCluster> allMLinkClusterList) {
		Vector<MLinkCongestionCluster> stRange_List = new Vector<MLinkCongestionCluster>();
		
		for (MLinkCongestionCluster mlCC : allMLinkClusterList) {
			if(mlCC.equals(coreMLinkCluster))
				continue;
			if (timeWithin(mlCC, coreMLinkCluster)&& spatialWithinByCentroid(mlCC, coreMLinkCluster))
				stRange_List.add(mlCC);
		}
		
		return stRange_List;		
	}

	// 时间间隔
	private boolean timeWithin(MLinkCongestionCluster mlCC1, MLinkCongestionCluster mlCC2) {
      		
       if((mlCC1.startTime - mlCC2.endTime)> this.maxRegionTimeInterval || (mlCC2.startTime - mlCC1.endTime) > this.maxRegionTimeInterval )
			return false;
		else
			return true;

	}

	// 空间距离(欧式距离的计算)
	private boolean spatialWithinByCentroid(MLinkCongestionCluster mlCC1, MLinkCongestionCluster mlCC2) {
		Point point1 = road.getPointByLinearReference(mlCC1.mLinkID, (mlCC1.startOffset+mlCC1.endOffset)/2);
		Point point2 = road.getPointByLinearReference(mlCC2.mLinkID, (mlCC2.startOffset+mlCC2.endOffset)/2);
		
		if (point1.distance(point2) < this.maxRegionDistance) {
			return true;
		} else
			return false;
	}
	
	private boolean spatialWithinByNearest(MLinkCongestionCluster mlCC1, MLinkCongestionCluster mlCC2) {
		
		LineString line1 = road.getLineByLinearReference(mlCC1.mLinkID, mlCC1.startOffset, mlCC1.endOffset);
		LineString line2 = road.getLineByLinearReference(mlCC2.mLinkID, mlCC2.startOffset, mlCC2.endOffset);
				
		return DistanceOp.isWithinDistance(line1, line2, this.maxRegionDistance);
		

	}
	
	public ArrayList <CongestionCluster> getVIPCongestionCluster(int minMLinkNum, int minDur, float length) {
		ArrayList <CongestionCluster> vipCongestionClusterList = new ArrayList <CongestionCluster> ();
	
		for(CongestionCluster cc : this.getCongestionCluster()){
			RegionCongestionCluster rcc = (RegionCongestionCluster)cc;
						
			int mlinkNum = rcc.mLinkTrafficSet.keySet().size();
			int dur = rcc.endTime - rcc.startTime + 1;
			float congestionLength = rcc.getTotalRoadLength();
			
			if (mlinkNum >= minMLinkNum && dur >= minDur && (congestionLength) > length) {
				vipCongestionClusterList.add(rcc);		
			}			
		}
		
		return vipCongestionClusterList;
	}
	
	//----------------------------------------------可视化输出----------------------------------------------------------//
	//获取多边形
	public void getVIPCongestionClusterMap(int minMLinkNum, int minDur, float length) throws Exception {
		//指定SHAPE文件
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
		chooser.setDialogTitle("Save a new shapefile for muliple congestion clusters");
		chooser.setSaveFile(new File("D:\\VIPCongestionClusterRegionMap.shp"));

		if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
			return;
		}

		File outputShapefile = chooser.getSelectedFile();
		
        //指定SHAPE类型
		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		Map<String, Serializable> params = null;
		ShapefileDataStore writeDataStore = null;
		SimpleFeatureType TYPE = null;

		try {
			params = new HashMap<String, Serializable>();
			params.put("url", outputShapefile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			writeDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
			TYPE = DataUtilities.createType("CongestionRegion",
					"GEOM:Polygon:srid=32650," 
			        + "ID:String,"
					+ "STIME:int," 
			        + "ETIME:int,"
					+ "DUR:int,"
			        + "MLNUM:int"
					);

			writeDataStore.createSchema(TYPE);
			writeDataStore.forceSchemaCRS(CRS.decode("EPSG:32650", true));

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (SchemaException e) {
			e.printStackTrace();
		}

		// prepare new feature collection
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
		SimpleFeature feature = null;
		MultiLineString line = null;

		RegionCongestionCluster congestionCluster = null;

		String[] clusterIDs = new String[congestionClusterSet.size()];
		congestionClusterSet.keySet().toArray(clusterIDs);

		for (String clusterID : clusterIDs) {
			congestionCluster = (RegionCongestionCluster)congestionClusterSet.get(clusterID);
			int mlinkNum = congestionCluster.mLinkTrafficSet.keySet().size();
			int dur = congestionCluster.endTime - congestionCluster.startTime + 1;
			float congestionLength = congestionCluster.getTotalRoadLength();
			
			if (mlinkNum >= minMLinkNum && dur >= minDur && (congestionLength) > length) {
				int sTime = congestionCluster.startTime;
				int eTime = congestionCluster.endTime;
				Polygon mbr = this.getMBRPolygon(congestionCluster.MBR);
				
				// create feature
				featureBuilder.add(mbr);
				featureBuilder.add(clusterID);
				featureBuilder.add(sTime);
				featureBuilder.add(eTime);
				featureBuilder.add(dur);
				featureBuilder.add(mlinkNum);
				feature = featureBuilder.buildFeature(null);
				collection.add(feature);
			}
		}

		// create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore.getFeatureSource(typeName);

		// create shape file
		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out
					.println(typeName + " does not support read/write access");
		}

	}
	
	public void getVIPCongestionClusterDetailMap(int minMLinkNum, int minDur, float length) throws Exception{
		//指定SHAPE文件
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
		chooser.setDialogTitle("Save a new shapefile for muliple congestion clusters");
		chooser.setSaveFile(new File("D:\\VIPCongestionClusterRegionDetailMap.shp"));

		if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
			return;
		}

		File outputShapefile = chooser.getSelectedFile();
		
        //指定SHAPE类型
		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		Map<String, Serializable> params = null;
		ShapefileDataStore writeDataStore = null;
		SimpleFeatureType TYPE = null;

		try {
			params = new HashMap<String, Serializable>();
			params.put("url", outputShapefile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			writeDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
			TYPE = DataUtilities.createType("CongestionRegion",
					"GEOM:MultiLineString:srid=32650," 
			        + "CID:String,"
			        + "MID:String,"	
					+ "STIME:int," 
			        + "ETIME:int,"
					+ "DUR:int,"
					+ "NUM:int"
					);

			writeDataStore.createSchema(TYPE);
			writeDataStore.forceSchemaCRS(CRS.decode("EPSG:32650", true));

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (SchemaException e) {
			e.printStackTrace();
		}

		// prepare new feature collection
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
		SimpleFeature feature = null;
		MultiLineString interval = null;

		RegionCongestionCluster congestionCluster = null;

		String[] clusterIDs = new String[congestionClusterSet.size()];
		congestionClusterSet.keySet().toArray(clusterIDs);

		for (String clusterID : clusterIDs) {
			congestionCluster = (RegionCongestionCluster)congestionClusterSet.get(clusterID);
			
			int mlinkNum = congestionCluster.mLinkTrafficSet.keySet().size();
			int dur = congestionCluster.endTime - congestionCluster.startTime + 1;
			float congestionLength = congestionCluster.getTotalRoadLength();
			
			if (mlinkNum >= minMLinkNum && dur >= minDur && (congestionLength) > length) {
				
				for(MLinkAggTraffic mLinkAggTraffic : congestionCluster.mLinkTrafficSet.values()){
					
					LineString line = this.road.getLineByLinearReference(mLinkAggTraffic.mLinkID, mLinkAggTraffic.startOffset, mLinkAggTraffic.endOffset);
					
					interval = new GeometryFactory().createMultiLineString(new LineString[]{line});
					
					// create feature
					featureBuilder.add(interval);
					featureBuilder.add(clusterID);
					featureBuilder.add(mLinkAggTraffic.mLinkID);
					featureBuilder.add(mLinkAggTraffic.startTime);
					featureBuilder.add(mLinkAggTraffic.endTime);
					featureBuilder.add(mLinkAggTraffic.dur);
					featureBuilder.add(mLinkAggTraffic.num);
					feature = featureBuilder.buildFeature(null);
					collection.add(feature);				
				}
			}
			
		}

		// create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore.getFeatureSource(typeName);

		// create shape file
		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out
					.println(typeName + " does not support read/write access");
		}
	}
	
	//------------------------------------------------------------------------------------------------------------------------//
	private Polygon getMBRPolygon(Envelope mbr) throws com.vividsolutions.jts.io.ParseException{	
	    GeometryFactory factory = new GeometryFactory();
	    WKTReader wktRdr = new WKTReader(factory);
		
		double minX = mbr.getMinX();
		double minY = mbr.getMinY();
		double maxX = mbr.getMaxX();
		double maxY = mbr.getMaxY();

	    String coords = minX + " " + minY + ", " + maxX + " " + minY+ ", " + maxX+ " " + maxY+ ", " + minX+ " " + maxY+ ", " + minX+ " " + minY ;
	    
	    String wktA = "POLYGON((" + coords + "))";
	    
		return (Polygon) wktRdr.read(wktA);
	}
	
	public static void main(String[] args) throws ParseException {
		STRegionCongestionClustering stCongestionCluster = new STRegionCongestionClustering();
		
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
				
<<<<<<< .mine
		stCongestionCluster.buildCongestionCluster("", 8, 8, dateFormat.parse("2009-3-8"),  600,  1); // 1 代表相交或相接
=======
		stCongestionCluster.buildCongestionCluster("Network", 8, 8, dateFormat.parse("2009-3-8"),  300,  1); // 1 代表相交或相接
>>>>>>> .r27

		System.out.println("Time cost : " + stCongestionCluster.timeCost);
		
		// 遍历簇
		ArrayList<CongestionCluster> congestionClusters = stCongestionCluster.getVIPCongestionCluster(2, 6, 500);
		
		for (CongestionCluster congestionCluster : congestionClusters) {
			RegionCongestionCluster cc = (RegionCongestionCluster)congestionCluster;
			if(cc.mLinkTrafficSet.keySet().size() < 2)
				continue;
			System.out.print("CluterID: " + cc.clusterID + ",");
			System.out.print(" StartTime: " + (cc.startTime-96) + ",");
			System.out.print(" EndtTime: " + (cc.endTime - 96));
			System.out.print("MLinkNum: " + cc.mLinkNum);
			System.out.print("Dur: " + cc.dur);
			System.out.print("Length: " + cc.getTotalRoadLength());
//			System.out.print(" SOFFSET: " + cc.startOffset );
//			System.out.print(" EOFFSET: " + cc.endOffset );
			System.out.println();
		}
		
		try {
			stCongestionCluster.getVIPCongestionClusterMap(2, 6, 500);
		    //stCongestionCluster.getVIPCongestionClusterDetailMap(2, 6, 500);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
